#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <cassert>

using namespace std;

#define PATHNAME "." // 项目名
#define PROJID 0x29C // 项目编号

// 两条管道名
const char *fifo_name1 = "fifo1";
const char *fifo_name2 = "fifo2";

enum
{
    SERVER = 0,
    CLIENT = 1
};

class shm
{
public:
    shm(int id)
        : _id(id)
    {
        _key = getKey(); // 获取key

        // 根据不同的身份
        // 创建 / 打开 共享内存
        // 创建 / 打开 命名管道
        if (_id == SERVER)
        {
            _shmid = shmHelper(_key, gsize, IPC_CREAT | IPC_EXCL | mode);

            int n = mkfifo(fifo_name1, mode);
            assert(n != -1);
            (void)n;

            n = mkfifo(fifo_name2, mode);
            assert(n != -1);
            (void)n;

            // 服务端以写打开命名管道1，以读打开命名管道2
            _wfd = open(fifo_name1,O_WRONLY);
            _rfd = open(fifo_name2, O_RDONLY);
        }

        else
        {
            _shmid = shmHelper(_key, gsize, IPC_CREAT);

            // 客服端以读打开命名管道1，以写打开命名管道2
            _rfd = open(fifo_name1, O_RDONLY);
            _wfd = open(fifo_name2, O_WRONLY);
        }

        // 关联共享内存
        _start = shmat(_shmid, NULL, 0); // 关联
        if (_start == (void *)-1)
        {
            cerr << "shmat fail!"
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(1);
        }
    }

    ~shm()
    {
        // 关闭fd
        close(_wfd);
        close(_rfd);

        // 去关联
        int n = shmdt(_start);
        if (n == -1)
        {
            cerr << "shmdt fail!"
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(1);
        }

        // 根据不同的身份:
        //    判断是否需要删除管道文件
        //    判断是否需要删除共享内存
        if (_id == SERVER)
        {
            unlink(fifo_name1);
            unlink(fifo_name2);
            shmctl(_shmid, IPC_RMID, NULL);
        }
    }

    key_t getKey() const
    {
        key_t key = ftok(PATHNAME, PROJID);

        if (key == -1)
        {
            // 失败，终止进程
            cerr << "ftok fail!  "
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(1);
        }

        return key;
    }

    int getShmID() const
    {
        return _shmid;
    }

    void *getStart() const
    {
        return _start;
    }

    int getWFD() const
    {
        return _wfd;
    }

    int getRFD() const
    {
        return _rfd;
    }

protected:
    static const int gsize = 4096;
    static const mode_t mode = 0666;

    // 共享内存助手
    int shmHelper(key_t key, size_t size, int flags)
    {
        int shmid = shmget(key, size, flags);
        if (shmid == -1)
        {
            // 失败，终止进程
            cerr << "shmget fail!  "
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(2);
        }

        return shmid;
    }

private:
    key_t _key;
    int _shmid = 0;
    void *_start;
    int _wfd; // 写端 与 读端 fd
    int _rfd;
    int _id; // 身份标识符，用来区分服务端与客户端
};
